package com.baofu.paycore.biz.task;

import com.baofu.paycore.biz.notice.ProductHandler;
import com.baofu.paycore.biz.paycore.converter.BO2BOConverter;
import com.baofu.paycore.biz.paycore.handler.BaseHandler;
import com.baofu.paycore.biz.paycore.handler.PayBizManager;
import com.baofu.paycore.biz.paycore.handler.impl.*;
import com.baofu.paycore.biz.tools.BuildModel;
import com.baofu.paycore.common.constant.PayCoreConstant;
import com.baofu.paycore.common.enums.BizCmdTypeEnum;
import com.baofu.paycore.common.enums.RedisKeyEnum;
import com.baofu.paycore.manager.model.PayBaseBO;
import com.baofu.paycore.service.facade.enums.ServiceCodeEnum;
import com.baofu.paycore.common.exception.PayCoreBizException;
import com.baofu.paycore.service.facade.enums.PayCoreErrorCode;
import com.baofu.paycore.dal.model.ErrorLogDO;
import com.baofu.paycore.manager.cache.RedisLockManager;
import com.baofu.paycore.manager.model.PayBO;
import com.baofu.paycore.manager.model.RechargeBO;
import com.baofu.paycore.manager.model.ResponseBO;
import com.baofu.paycore.manager.storage.impl.ErrorLogManager;
import com.baofu.paycore.service.facade.enums.PayStatusEnums;
import com.baofu.paycore.service.facade.enums.RequestSystemEnum;
import com.google.common.base.Splitter;
import com.system.dispatch.dal.model.BizCmdDO;
import com.system.dispatch.impl.BaseCmdHandler;
import lombok.extern.slf4j.Slf4j;
import org.jboss.logging.MDC;
import org.slf4j.helpers.SystemMarker;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.List;
import java.util.Objects;

/**
 * 账务重试处理
 *
 * <p>
 *  1.获得执行任务名称
 *  2.执行任务
 *  3.任务执行最后失败信息
 * </p>
 * User: xin deng  Date: 16/7/6 ProjectName: paycore Version: 1.0
 */
@Slf4j
@Component
public class AccRetryDispatch extends BaseCmdHandler {

    /**
     * 下次重试间隔时间
     */
    //1分钟 2分钟 5分钟 10分钟
    private int[] noticeSequence = new int[]{2*60, 5*60, 10*60};

    /** 锁 */
    @Autowired
    private RedisLockManager redisLockManager;

    /** 发kafka消息服务 */
    @Autowired
    private ProductHandler productHandler;

    /** 统一业务处理 */
    @Autowired
    private PayBizManager payBizManager;

    /** 消费服务 */
    @Autowired
    private ConsumeHandler consumeHandler;

    /** 充值服务 */
    @Autowired
    private RechargeHandler rechargeHandler;

    /** 提现服务 */
    @Autowired
    private WithdrawHandler withdrawHandler;

    /** 转账服务 */
    @Autowired
    private TransferHandler transferHandler;

    /** 转账撤销 */
    @Autowired
    private CancelTransferHandler cancelTransferHandler;

    /**冻结*/
    @Autowired
    private FreezeHandler freezeHandler;

    /**解冻*/
    @Autowired
    private UnfreezeHandler unfreezeHandler;

    /** 消费服务 */
    @Resource(name = "webConsumerHandler")
    private BaseHandler<PayBO,RechargeBO> webConsumerHandler;

    /** 差错日志Manager */
    @Autowired
    private ErrorLogManager errorLogManager;

    /** 线下打款充值订单处理handler */
    @Autowired
    private OfflineRechargeHandler offlineRechargeHandler;

    /** 退款服务Handler */
    @Autowired
    private RefundHandler refundHandler;

    /** 预授权Handler */
    @Autowired
    private PreAuthHandler preAuthHandler;

    /**预授权取消Handler*/
    @Autowired
    private PreAuthCancelHandler preAuthCancelHandler;

    /**预授权确认Handler*/
    @Autowired
    private PreAuthConfirmHandler preAuthConfirmHandler;

    /**解冻提现Handler*/
    @Autowired
    private UnfreezeWithdrawHandler unfreezeWithdrawHandler;

    /**解冻提现Handler*/
    @Autowired
    private UnfreezeTransferHandler unfreezeTransferHandler;
    /**
     * 执行任务
     *
     * @param command 任务
     * @throws Exception 异常
     */
    @Override
    protected void doCmd(BizCmdDO command) throws Exception {

        MDC.put(SystemMarker.TRACE_LOG_ID, command.getTraceLogId());
        long startTime = System.currentTimeMillis();
        String requestType;
        String payNo;
        boolean flag = false;
        try {
            //添加业务锁
            flag = redisLockManager.lock(RedisKeyEnum.PAY_ACC_RETRY, command.getBizId());
            if (StringUtils.isEmpty(command.getBizId())) {
                log.error("账务重试任务执行Dispatch异常 指令ID为空 command:{}", command);
                return;
            }
            retryInterval = setNextTime(command);

            List<String> strings = Splitter.on(PayCoreConstant.BASE_CMD_SEPARATOR).splitToList(command.getBizId());
            requestType = strings.get(0);
            payNo = strings.get(1);
            log.info("账务重试命令执行开始 请求服务类型:{} 支付单号:{}, 第：{}次重试, 共重试{}次 下次执行间隔时间:{}", requestType, payNo,
                    command.getRetryTimes(), command.getMaxRetryTimes(), retryInterval);
            BaseHandler baseHandler = getHandler(requestType);
            //主单
            PayBaseBO payBaseBO = baseHandler.queryOrderByPayNo(payNo);
            //重试处理
            ResponseBO responseBO = payBizManager.dalInnerFundProc(baseHandler, payBaseBO);

            if (!Objects.equals(PayStatusEnums.SUCCESS.getCode(), responseBO.getStatus()) &&
                    !Objects.equals(PayStatusEnums.FAIL.getCode(), responseBO.getStatus())) {
                log.error("本次重试不是最终状态:{},{},{},{}", responseBO.getStatus(), command.getBizId(),
                        responseBO.getErrorCode(), responseBO.getErrorMsg());
                throw new PayCoreBizException(PayCoreErrorCode.DISPATCH_RETRY_EXCEPTION, responseBO.getErrorMsg());
            }
            payBaseBO.setStatus(responseBO.getStatus());
            payBaseBO.setTradeLogId(command.getTraceLogId());
            //回调支付
            this.doConsume(payBaseBO, requestType);

            //发送消息
            productHandler.sendKafkaMsg(BO2BOConverter.getPayMessageDTO(payBaseBO, responseBO));

        }catch (Exception e){
            log.error("账务重试命令执行失败:{}", e);
            throw e;
        }finally {
            if(flag) {
                redisLockManager.release(RedisKeyEnum.PAY_ACC_RETRY, command.getBizId());
            }
            log.info("账务重试命令执行结束 指令ID:{} 耗时:{}ms", command.getBizId(), System.currentTimeMillis() - startTime);
        }

    }

    /**
     * 任务执行最后失败信息
     *
     * @param command 任务
     */
    @Override
    protected void failedFinally(BizCmdDO command) {

        log.error("账务重试处理 bizId:{},{}", command.getBizId(), command.getTraceLogId());
        List<String> strings = Splitter.on(PayCoreConstant.BASE_CMD_SEPARATOR).splitToList(command.getBizId());
        String requestType = strings.get(0);
        String payNo = strings.get(1);
        ErrorLogDO errorLogDO = BuildModel.buildErrorLog(payNo, null, BizCmdTypeEnum.ACC_RETRY.getCode(),
                requestType, PayCoreErrorCode.DISPATCH_RETRY_EXCEPTION.getErrorCode(),
                command.getFailReason(), "重试失败");
        errorLogManager.addOrder(errorLogDO);
    }

    /**
     * 获取业务实现类
     *
     * @param requestType 请求业务类型
     * @return 实现类
     */
    private BaseHandler getHandler(String requestType) {

        ServiceCodeEnum serviceCode = ServiceCodeEnum.explain(requestType);
        if (serviceCode == null) {
            log.error("暂不支持此业务：{}", requestType);
            throw new PayCoreBizException(PayCoreErrorCode.NOT_IN_SERVICE);
        }
        BaseHandler handler;
        switch (serviceCode){
            case PAY:
                handler = consumeHandler;
                break;
            case RECHARGE:
                handler = rechargeHandler;
                break;
            case WITHDRAW:
                handler = withdrawHandler;
                break;
            case TRANSFER:
                handler = transferHandler;
                break;
            case CANCEL:
                handler = cancelTransferHandler;
                break;
            case OFFLINE_RECHARGE:
                handler = offlineRechargeHandler;
                break;
            case REFUND:
                handler = refundHandler;
                break;
            case AUTH:
                handler = preAuthHandler;
                break;
            case AUTH_CONFIRM:
                handler = preAuthConfirmHandler;
                break;
            case AUTH_CANCEL:
                handler = preAuthCancelHandler;
                break;
            case UNFRE_WITHDRAW:
                handler = unfreezeWithdrawHandler;
                break;
            case FREEZE:
                handler=freezeHandler;
                break;
            case UNFREEZE:
                handler=unfreezeHandler;
                break;
            case ISSUE_WITHDRAW:
                handler=unfreezeWithdrawHandler;
                break;
            case UNFRE_TRANSFER:
                handler=unfreezeTransferHandler;
                break;
            default:
                log.error("暂不支持此业务：{}", requestType);
                throw new PayCoreBizException(PayCoreErrorCode.NOT_IN_SERVICE);
        }
        return handler;
    }

    /**
     * 支付回调
     *
     * @param rechargeBO        充值订单
     */
    private void doConsume(PayBaseBO rechargeBO, String requestType){
        if(!Objects.equals(RequestSystemEnum.PAYCORE.getCode(),rechargeBO.getRequestSystem()) ||
                !Objects.equals(ServiceCodeEnum.RECHARGE.getCode(), requestType)){
            log.info("充值回调:{} 请求业务类型:{}",rechargeBO.getRequestSystem(), requestType);
            return;
        }
        //消费回调
        PayBaseBO payBaseBO = webConsumerHandler.callBack((RechargeBO) rechargeBO);
        if (!Objects.equals(PayStatusEnums.SUCCESS.getCode(), payBaseBO.getStatus()) &&
                !Objects.equals(PayStatusEnums.FAIL.getCode(), payBaseBO.getStatus())) {
            log.error("本次重试不是最终状态:{},{},{}", payBaseBO.getStatus(), payBaseBO.getErrorCode(),
                    payBaseBO.getErrorMsg());
            throw new PayCoreBizException(PayCoreErrorCode.DISPATCH_RETRY_EXCEPTION, payBaseBO.getErrorMsg());
        }
    }

    /**
     * 设置下次执行时间
     *
     * @param command           任务命令
     * @return                  间隔时间
     */
    private int setNextTime(BizCmdDO command){
        long currentTime  = command.getRetryTimes() > 3 ? 3 : command.getRetryTimes();
        int minutes = 0;
        for (int i = 0; i < currentTime; i++) {
            minutes += noticeSequence[i];
        }
        return minutes;
    }
}
